package com.swapper.hls.tag;

import com.swapper.hls.internal.enumerated.KeyMethod;
import com.swapper.hls.internal.info.Attribute;
import com.swapper.hls.internal.info.Tag;

import java.net.URI;
import java.util.regex.Pattern;

@Tag(grouping = Tag.Grouping.MediaSegment)
public final class KeyTag extends AbstractTag {
  public static final String TAG_NAME = "#EXT-X-KEY";
  private final MethodAttribute mMethod;
  private UriAttribute mUri;
  private IvAttribute mIv;
  private KeyFormatAttribute mKeyFormat;
  private KeyFormatVersionsAttribute mKeyFormatVersions;

  public KeyTag() {
    this(KeyMethod.NONE, null);
  }

  public KeyTag(KeyMethod method, URI uri) {
    this(method, uri, null);
  }

  public KeyTag(KeyMethod method, URI uri, String iv) {
    this(method, uri, iv, null);
  }

  public KeyTag(KeyMethod method, URI uri, String iv, String keyFormat) {
    this(method, uri, iv, keyFormat, null);
  }

  public KeyTag(KeyMethod method, URI uri, String iv,
                String keyFormat, String keyFormatVersions) {
    super(TAG_NAME);
    mMethod = new MethodAttribute(method);
    if (uri == null) {
      if (method != KeyMethod.NONE) {
        throw new IllegalArgumentException("This URI is REQUIRED unless the METHOD is NONE.");
      }
    } else {
      mUri = new UriAttribute(uri);
    }
    if (iv != null) {
      mIv = new IvAttribute(iv);
    }
    if (keyFormat != null) {
      mKeyFormat = new KeyFormatAttribute(keyFormat);
    }
    if (keyFormatVersions != null) {
      mKeyFormatVersions = new KeyFormatVersionsAttribute(keyFormatVersions);
    }
  }

  @Attribute(necessity = Attribute.Necessity.Required)
  public static final class MethodAttribute extends AbstractAttribute<KeyMethod> {
    public static final String ATTRIBUTE_NAME = "METHOD";

    private MethodAttribute(KeyMethod value) {
      super(ATTRIBUTE_NAME, value);
    }

    @Override
    public KeyMethod verify(KeyMethod value) {
      if (value == null) {
        throw new NullPointerException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is null.");
      }
      return value;
    }
  }

  @Attribute(necessity = Attribute.Necessity.Condition)
  public static final class UriAttribute extends AbstractAttribute<URI> {
    public static final String ATTRIBUTE_NAME = "URI";

    private UriAttribute(URI value) {
      super(ATTRIBUTE_NAME, value);
    }

    @Override
    public URI verify(URI value) {
      if (value == null) {
        throw new NullPointerException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is null.");
      }
      return value;
    }

    @Override
    public String toString() {
      return mName + '=' + '"' + mValue + '"';
    }
  }

  @Attribute(necessity = Attribute.Necessity.Required, since = 2)
  public static final class IvAttribute extends AbstractAttribute<String> {
    public static final String ATTRIBUTE_NAME = "IV";
    public static final Pattern PATTERN = Pattern.compile("^0[xX][0-9a-fA-F]{32}$");

    private IvAttribute(String value) {
      super(ATTRIBUTE_NAME, value);
    }

    @Override
    public String verify(String value) {
      if (value == null) {
        throw new NullPointerException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is null.");
      }
      if (!PATTERN.matcher(value).matches()) {
        throw new IllegalArgumentException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is mismatching.");
      }
      return value;
    }
  }

  @Attribute(necessity = Attribute.Necessity.Optional, since = 5)
  public static final class KeyFormatAttribute extends AbstractAttribute<String> {
    public static final String ATTRIBUTE_NAME = "KEYFORMAT";

    private KeyFormatAttribute(String value) {
      super(ATTRIBUTE_NAME, value);
    }

    @Override
    public String verify(String value) {
      if (value == null) {
        throw new NullPointerException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is null.");
      }
      return value;
    }

    @Override
    public String toString() {
      return mName + '=' + '"' + mValue + '"';
    }
  }

  @Attribute(necessity = Attribute.Necessity.Optional, since = 5)
  public static final class KeyFormatVersionsAttribute extends AbstractAttribute<String> {
    public static final String ATTRIBUTE_NAME = "KEYFORMATVERSIONS";
    public static final Pattern PATTERN = Pattern.compile("^[1-5](/[1-5])*$");

    private KeyFormatVersionsAttribute() {
      this("1");
    }

    private KeyFormatVersionsAttribute(String value) {
      super(ATTRIBUTE_NAME, value);
    }

    @Override
    public String verify(String value) {
      if (value == null) {
        throw new NullPointerException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is null.");
      }
      if (!PATTERN.matcher(value).matches()) {
        throw new IllegalArgumentException("HLS tag(" + TAG_NAME + ") attribute(" + ATTRIBUTE_NAME + ") is mismatching.");
      }
      return value;
    }

    @Override
    public String toString() {
      return mName + '=' + '"' + mValue + '"';
    }
  }

  @Override
  public String toString() {
    StringBuilder builder = new StringBuilder();
    builder.append(TAG_NAME).append(':').append(mMethod);
    if (mUri != null) {
      builder.append(',').append(mUri);
    }
    if (mIv != null) {
      builder.append(',').append(mIv);
    }
    if (mKeyFormat != null) {
      builder.append(',').append(mKeyFormat);
    }
    if (mKeyFormatVersions != null) {
      builder.append(',').append(mKeyFormatVersions);
    }
    return builder.toString();
  }
}
